#include <common.h>
#include <pci.h>
#include <dm.h>

#include <asm/io.h>

static void pci_set_irq(int bus, int device, u8 irq)
{
	struct udevice *dev;
	pci_dev_t busdevfunc;
	void *base;
	int func_num;
	int ret;
	u32 device_id;

	for (func_num=0; func_num<8; func_num++) {
		busdevfunc = PCI_BDF(bus, device, func_num);
		ret = dm_pci_bus_find_bdf(busdevfunc, &dev);
		if (!ret) {
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			dev_info(NULL, "pcie (%04X,%X) %p irq=%d\n", device_id, func_num, base, irq);
		}
	}
}

//linux-4.19的内核由于不能自动配置pcie设备，这里需要给pcie设备分配一个中断号
//linux-5.10的pcie控制器驱动不同，则不存在这个问题
static void ls7a_pcie_irq_fixup(void)
{
	pci_dev_t busdevfunc;
	void *base;
	int i, dev_num;
	u8 header_type;
	u32 device_id, bus, irq;

	device_id = PCI_DEVICE_ID_LOONGSON_APB;
	for (i=0; i<1; i++) {
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 0 + i;//irq号参考7a1000数据手册得到，或者参考内核源码设备树、pmon源码得到
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);//中断号加上64，64是根据内核分配的irq计算得到的
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_DC;
	for (i=1; i>=0; i--) {
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 28 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_HDA;
	for (i=0; i<1; i++) {
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 58 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_GMAC;
	for (i=0; i<2; i++) {//7a1000有2个gmac
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 12 + i * 2;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_EHCI;
	for (i=0; i<2; i++) {//7a1000有2个ehci
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 48 + i * 2;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_OHCI;
	for (i=0; i<2; i++) {//7a1000有2个ohci
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 49 + i * 2;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_SATA;
	for (i=0; i<3; i++) {//7a1000有3个sata
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, i);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 16 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}

	device_id = PCI_DEVICE_ID_LOONGSON_SPI;
	for (i=0; i<1; i++) {
		busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, device_id, 0);
		if (busdevfunc == -1) {
		} else {
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			irq = 58 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			dev_info(NULL, "pcie dev (%04X,%04X,%X) %p irq=%d\n", PCI_VENDOR_ID_LOONGSON, device_id, i, base, irq);
		}
	}
	
	//给pcie桥级挂载到pcie桥的外设分配中断号
	bus = 0;
	//pcie_f0_0 irq=32  pcie_f0_1 irq=33 pcie_f0_2 irq=34 pcie_f0_3 irq=35
	//pcie_f1_0 irq=36  pcie_f1_1 irq=37
	for (i=0; i<6; i++) {
		//这里9是根据pcie设备扫描顺序得到的设备号，或者根据7a1000的数据手册对应的pcie桥设备号
		//如果扫描顺序改变这个号应该需要改变，目前写死了。
		busdevfunc = PCI_BDF(0, 9 + i, 0);
		pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
		if (device_id != 0xffffffff) {
			irq = 32 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			dev_info(NULL, "pcie bridge (%04X,%X) %p irq=%d\n", device_id, i, base, irq);
			//每个pcie桥只能挂一个pcie设备，比起bus号加1
			bus += 1;
			busdevfunc = PCI_BDF(bus, 0, 0);
			pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
			if (device_id != 0xffffffff) {
				u32 bar_response;
				pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
				pci_read_config_dword(busdevfunc, PCI_BASE_ADDRESS_0, &bar_response);
				dev_info(NULL, "pcie bridge (%04X,%X) %x irq=%d\n", device_id, i, bar_response, irq);
			}
			pci_read_config_byte(busdevfunc, PCI_HEADER_TYPE, &header_type);
			switch (header_type & 0x03) {
			case PCI_HEADER_TYPE_BRIDGE:	/* PCI-to-PCI bridge */
				bus += 1;
				for (dev_num=0; dev_num<8; dev_num++) {
					pci_set_irq(bus, dev_num, irq);
				}
				break;
			}
		}
	}

	//pcie_g0_0 irq=40  pcie_g0_1 irq=41 pcie_g1_0 irq=42 pcie_g1_1 irq=43
	for (i=0; i<4; i++) {
		busdevfunc = PCI_BDF(0, 15 + i, 0);
		pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
		if (device_id != 0xffffffff) {
			irq = 40 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			dev_info(NULL, "pcie bridge (%04X,%X) %p irq=%d\n", device_id, i, base, irq);

			bus += 1;
			busdevfunc = PCI_BDF(bus, 0, 0);
			pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
			if (device_id != 0xffffffff) {
				u32 bar_response;
				pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
				pci_read_config_dword(busdevfunc, PCI_BASE_ADDRESS_0, &bar_response);
				dev_info(NULL, "pcie bridge (%04X,%X) %x irq=%d\n", device_id, i, bar_response, irq);
			}
			pci_read_config_byte(busdevfunc, PCI_HEADER_TYPE, &header_type);
			switch (header_type & 0x03) {
			case PCI_HEADER_TYPE_BRIDGE:	/* PCI-to-PCI bridge */
				bus += 1;
				for (dev_num=0; dev_num<8; dev_num++) {
					pci_set_irq(bus, dev_num, irq);
				}
				break;
			}
		}
	}

	//pcie_h0 irq=38  pcie_h1 irq=39
	for (i=0; i<2; i++) {
		busdevfunc = PCI_BDF(0, 19 + i, 0);
		pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
		if (device_id != 0xffffffff) {
			irq = 38 + i;
			pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
			base = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
			dev_info(NULL, "pcie bridge (%04X,%X) %p irq=%d\n", device_id, i, base, irq);

			bus += 1;
			busdevfunc = PCI_BDF(bus, 0, 0);
			pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
			if (device_id != 0xffffffff) {
				u32 bar_response;
				pci_write_config_byte(busdevfunc, PCI_INTERRUPT_LINE, irq + 64);
				pci_read_config_dword(busdevfunc, PCI_BASE_ADDRESS_0, &bar_response);
				dev_info(NULL, "pcie bridge (%04X,%X) %x irq=%d\n", device_id, i, bar_response, irq);
			}
			pci_read_config_byte(busdevfunc, PCI_HEADER_TYPE, &header_type);
			switch (header_type & 0x03) {
			case PCI_HEADER_TYPE_BRIDGE:	/* PCI-to-PCI bridge */
				bus += 1;
				for (dev_num=0; dev_num<8; dev_num++) {
					pci_set_irq(bus, dev_num, irq);
				}
				break;
			}
		}
	}
}
/*
static void clear_pcie_inter_irq(void)
{
	pci_dev_t busdevfunc;
	u32 i, device_id, val;
	u32 addr;

	for (i=9; i<21; i++) {
		busdevfunc = PCI_BDF(0, i, 0);
		pci_read_config_dword(busdevfunc, PCI_VENDOR_ID, &device_id);
		if (device_id != 0xffffffff) {
			pci_read_config32(busdevfunc, PCI_BASE_ADDRESS_0, &addr);
			addr &= PCI_BASE_ADDRESS_MEM_MASK;
			addr = pci_mem_to_phys(busdevfunc, addr); dev_info(NULL, "pcie bridge (%04X,%X) %x\n", device_id, i, addr);
			val = readl((void *)addr + 0x18);
			if (val) {
				writel(val, (void *)addr + 0x1c);
			}
		}
	}
}
*/

extern void loongson_ht_trans_init(void);

void ls_pcie_fixup(int port_fix)
{
//	clear_pcie_inter_irq();
	ls7a_pcie_irq_fixup();
#ifdef CONFIG_CPU_LOONGSON3A4000
	loongson_ht_trans_init();
#endif
}
